﻿using Abp.Domain.Entities;
using Abp.Specifications;
using Hicap.QRCodeWarehouse.Core.EFCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Hicap.QRCodeWarehouse.Core.Managers
{
    public abstract class ManagerBase<TEntity> : IManagerBase<TEntity> where TEntity : class, IEntity, ISoftDelete
    {
        protected List<Expression<Func<TEntity, dynamic>>> ForeignKeys { get; set; } = new List<Expression<Func<TEntity, dynamic>>>();
        protected List<Expression<Func<TEntity, bool>>> QueryCondition { get; set; } = new List<Expression<Func<TEntity, bool>>>();
        protected List<Action<TEntity>> CreateActions { get; set; } = new List<Action<TEntity>>();
        protected List<Action<TEntity>> UpdateActions { get; set; } = new List<Action<TEntity>>();
        protected List<Action<TEntity>> DeleteActions { get; set; } = new List<Action<TEntity>>();
        protected List<Func<TEntity, bool>> CreateCheckActions { get; set; } = new List<Func<TEntity, bool>>();
        protected List<Func<TEntity, bool>> UpdateCheckActions { get; set; } = new List<Func<TEntity, bool>>();
        protected List<Func<TEntity, bool>> DeleteCheckActions { get; set; } = new List<Func<TEntity, bool>>();

        protected readonly IUnitOfWork UnitOfWork;
        protected readonly IRepository<TEntity> Repository;

        protected ManagerBase(IUnitOfWork unitOfWork)
        {
            UnitOfWork = unitOfWork;
            Repository = UnitOfWork.GetRepository<TEntity>();
        }

        public virtual IQueryable<TEntity> GetQuery(Expression<Func<TEntity, bool>> predicate)
        {
            IQueryable<TEntity> query = Repository.GetIncludingQuery(ForeignKeys.ToArray());
            Expression<Func<TEntity, bool>> expression = predicate;
            QueryCondition?.ForEach(x => { expression = expression == null ? x : expression.And(x); });

            if (expression != null) return query.Where(expression);
            return query;
        }

        public async Task<List<dynamic>> GetAll()
        {
            IQueryable<TEntity> query = GetQuery(null);
            List<TEntity> list = await query.ToListAsync();
            return await list.ToDynamicListAsync();
        }

        public async Task<int> Insert(TEntity entity, List<Action<TEntity>> extraActions = null)
        {
            if (CreateCheckActions.Any(x => !x(entity))) return 0;

            if (extraActions == null)
                CreateActions.ForEach(x => x?.Invoke(entity));
            else
                extraActions.ForEach(x => x?.Invoke(entity));

            await Repository.InsertAsync(entity);
            return await UnitOfWork.SaveChangesAsync(true);
        }

        public async Task<int> InsertAndGetId(TEntity entity, List<Action<TEntity>> extraActions = null)
        {
            if (CreateCheckActions.Any(x => !x(entity))) return -1;

            if (extraActions == null)
                CreateActions.ForEach(x => x?.Invoke(entity));
            else
                extraActions.ForEach(x => x?.Invoke(entity));
            int id = await Repository.InsertAndGetIdAsync(entity);
            await UnitOfWork.SaveChangesAsync(true);
            return id;
        }

        public Task<bool> CheckHealth()
        {
            return Task.Run(() => Repository.CheckHealth());
        }

        public async Task<int> Update(TEntity entity, List<Action<TEntity>> extraActions = null)
        {
            if (UpdateCheckActions.Any(x => !x(entity))) return 0;

            if (extraActions == null)
                UpdateActions.ForEach(x => x?.Invoke(entity));
            else
                extraActions.ForEach(x => x?.Invoke(entity));

            await Repository.UpdateAsync(entity);
            return await UnitOfWork.SaveChangesAsync(true);
        }

        public async Task<int> InsertOrUpdate(TEntity entity)
        {
            if (entity.IsTransient())
            {
                if (CreateCheckActions.Any(x => !x(entity))) return 0;
                CreateActions.ForEach(x => x(entity));
            }
            else
            {
                if (UpdateCheckActions.Any(x => !x(entity))) return 0;
                UpdateActions.ForEach(x => x(entity));
            }
            await Repository.InsertOrUpdateAsync(entity);
            return await UnitOfWork.SaveChangesAsync(true);
        }

        public async Task<int> InsertOrUpdateAndGetId(TEntity entity)
        {
            if (entity.IsTransient())
            {
                if (CreateCheckActions.Any(x => !x(entity))) return -1;
                CreateActions.ForEach(x => x(entity));
            }
            else
            {
                if (UpdateCheckActions.Any(x => !x(entity))) return -1;
                UpdateActions.ForEach(x => x(entity));
            }
            int id = await Repository.InsertOrUpdateAndGetIdAsync(entity);
            await UnitOfWork.SaveChangesAsync(true);
            return id;
        }

        private async Task Delete(TEntity entity)
        {
            if (DeleteCheckActions.Any(x => !x(entity))) return;
            DeleteActions.ForEach(x => x(entity));
            await Repository.DeleteAsync(entity);
        }

        public async Task<int> Delete(int id)
        {
            TEntity entity = await Repository.GetAsync(id);
            await Delete(entity);
            return await UnitOfWork.SaveChangesAsync(true);
        }

        public async Task<int> BatchInsertOrUpdate(List<TEntity> list)
        {
            if (CreateCheckActions.Any(x => !list.Any(x))) return 0;
            if (UpdateCheckActions.Any(x => !list.Any(x))) return 0;
            CreateActions.ForEach(list.ForEach);
            UpdateActions.ForEach(list.ForEach);

            list.ForEach(entity =>
            {
                if (entity.IsTransient())
                {
                    if (CreateCheckActions.Any(x => !x(entity))) return;
                    CreateActions.ForEach(x => x(entity));
                }
                else
                {
                    if (UpdateCheckActions.Any(x => !x(entity))) return;
                    UpdateActions.ForEach(x => x(entity));
                }
            });

            await Repository.BatchInsertOrUpdateAsync(list);
            return await UnitOfWork.SaveChangesAsync(true);
        }

        private async Task<int> BatchDelete(List<TEntity> list)
        {
            if (DeleteCheckActions.Any(x => !list.Any(x))) return 0;
            DeleteActions.ForEach(list.ForEach);
            list.ForEach(async x => await Delete(x));
            return await UnitOfWork.SaveChangesAsync(true);
        }

        public async Task<int> BatchDelete(List<int> list)
        {
            List<TEntity> enities = GetQuery(x => list.Any(y => y == x.Id)).ToList();
            return await BatchDelete(enities);
        }
    }
}